package com.vlyman.shiro.jwt;

import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.vlyman.consts.KeyConsts;
import com.vlyman.exception.ShiroException;
import com.vlyman.rtn.SystemRtn;
import com.vlyman.utils.AesUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.authc.AccountException;
import org.apache.shiro.codec.Base64;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import java.util.Date;

/**
 * 类描述：
 *  JWT工具类
 * @author chenjs
 * @date 2019/1/9 16:55
 **/
@Slf4j
@Component
public class JwtUtil {

    /**
     * token超时时间，不设置默认为7天（604800秒）
     */
    private static Long tokenExpireSecond;

    /**
     * JWT认证加密私钥(Base64加密)
     */
    private static String encryptKey;

    /**
     * cache缓存过期时间，不设置默认为1天（86400秒）
     */
    public static Integer cacheExpireSecond;

    /**
     * 方法描述：
     *   获取Token中的公共信息
     *  @author chenjs
     *  @param token
     *  @param claimName
     *  @return String
     *  @date 2019/1/9 18:38
     */
    public static String getClaim(String token, String claimName) throws ShiroException{
        try {
            DecodedJWT jwt = JWT.decode(token);
            // 只能输出String类型，如果是其他类型返回null
            return jwt.getClaim(claimName).asString();
        } catch (Exception e) {
            log.error("解密Token中的公共信息出现异常", e);
            throw new ShiroException("解密Token中的公共信息出现异常");
        }
    }

    /**
     * 方法描述：
     *   解密获取Token中的公共信息
     *  @author chenjs
     *  @param token
     *  @param claimName
     *  @return String 解密后的信息
     *  @date 2019/1/14 11:27
     */
    public static String getAESEncryptClaim(String token, String claimName) throws ShiroException{
        try {
            DecodedJWT jwt = JWT.decode(token);
            String encryptClaim = jwt.getClaim(claimName).asString();
            return AesUtil.decrypt(encryptClaim, KeyConsts.AES_SECRET_KEY);
        } catch (Exception e) {
            log.error("解密Token中的加密公共信息出现异常", e);
            throw new ShiroException("解密Token中的加密公共信息出现异常");
        }
    }

    /**
     * 方法描述：
     *   校验token是否正确
     *  @author chenjs
     *  @param token 待校验令牌
     *  @return boolean
     *  @date 2019/1/9 17:57
     */
    public static boolean verify(String token) {
        try {
            String seccret = getAESEncryptClaim(token, KeyConsts.JWT_CLAIM_USERID) + Base64.decodeToString(encryptKey);
            Algorithm algorithm = Algorithm.HMAC256(seccret);
            JWTVerifier verifier = JWT.require(algorithm).build();
            DecodedJWT jwt = verifier.verify(token);
            Date expireTime = jwt.getExpiresAt();
            if (System.currentTimeMillis() > expireTime.getTime()) {
                log.warn(SystemRtn.TOKEN_EXPIRED.getMsg());
                throw new AccountException(SystemRtn.TOKEN_EXPIRED.getCodeStr());
            }
            return true;
        } catch (Exception e) {
            log.error(SystemRtn.TOKEN_VALIDATE_ERROR.getMsg(), e);
            throw new AccountException(SystemRtn.TOKEN_VALIDATE_ERROR.getCodeStr());
        }
    }

    /**
     * 方法描述：
     *   生成签名
     *  @author chenjs
     *  @param userId
     *  @return 返回加密的Token
     *  @date 2019/1/9 23:38
     */
    public static String sign(Integer userId) {
        try {
            // 帐号加JWT私钥加密
            String secret = userId + Base64.decodeToString(encryptKey);
            long currentTimeMillis = System.currentTimeMillis();
            // 此处过期时间是以毫秒为单位，所以乘以1000
            Date date = new Date(currentTimeMillis + tokenExpireSecond * 1000);
            Algorithm algorithm = Algorithm.HMAC256(secret);
            String encryptUserId = AesUtil.encrypt(userId.toString(), KeyConsts.AES_SECRET_KEY);
            // 附带account帐号信息
            return JWT.create()
                    .withClaim(KeyConsts.JWT_CLAIM_USERID, encryptUserId)
                    .withClaim(KeyConsts.JWT_CLAIM_SIGNTIME, currentTimeMillis)
                    .withExpiresAt(date)
                    .sign(algorithm);
        } catch (Exception e) {
            log.error("生成jwt签名错误！", e);
            return null;
        }
    }

    @Value("${jwt.token.expire-second:604800}")
    public void setTokenExpireSecond(Long tokenExpireSecond) {
        JwtUtil.tokenExpireSecond = tokenExpireSecond;
    }

    @Value("${jwt.token.encrypt-key:dmx5bWFuLW1hbmFnZQ==}")
    public void setEncryptKey(String encryptKey) {
        JwtUtil.encryptKey = encryptKey;
    }

    @Value("${jwt.cache.expire-second:86400}")
    public void setCacheExpireSecond(Integer cacheExpireSecond) {
        JwtUtil.cacheExpireSecond = cacheExpireSecond;
    }
}
